// http://www.hzxljx.com
// https://www.ceshidafen.com/xingmingzi/

package main

import (
	"fmt"
	"strings"
	"io/ioutil"
	"net/http"
	"time"
	"regexp"
	"sync"
	"strconv"
	"runtime"
	"os"
)

const DefaultFormat = "2006-01-02 15:04:05"

var logFile *os.File

func LOG(message string) {
	var err error

	if logFile == nil {
		logFile, err = os.OpenFile(
			"./" + strconv.FormatInt(time.Now().Unix(), 10) + ".log",
			os.O_RDWR | os.O_APPEND | os.O_CREATE, os.ModePerm,
		)

		if err != nil {
			panic(err)
		}
	}

	logFile.Write([]byte(message + "\n"))
}

func checkError() {
	if err := recover(); err != nil {
		// fmt.Println("error：", err)
	}
}

func httpGet(url string) string {
	defer checkError()

	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	return string(body)
}

func httpPost(url string, params map[string]string) string {
	defer checkError()

	var args []string
	for k, v := range params {
		args = append(args, fmt.Sprintf("%s=%s", k, v))
	}

	resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(strings.Join(args, "&")))
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	return string(body)
}

func toDateTime(datetime string) (tmp time.Time, err error) {
	loc, err := time.LoadLocation("Local")
	if err != nil {
		return
	}

	tmp, err = time.ParseInLocation(DefaultFormat[0:len(datetime)], datetime, loc)
	if err != nil {
		return
	}

	return tmp, nil
}

//////////////////////////////////////////////////////

type beName struct {
	surname 	string		 				// 姓氏
	sex			string		 				// 性别
	birthday 	day			 				// 日期
	names       map[string]string   		// 姓名 => 分数
	nameCount   int32 						// 数量
	nameChan	chan string  				// 姓名通道
	scoreChan   chan map[string]string   	// 姓名分数通道
	lock        sync.RWMutex 			 	// 读写锁
}

func (b *beName) gender() int {
	if b.sex == "女" {
		return 1
	}
	return 0
}

func (b *beName) addName(name string) {
	b.nameChan <- name
}

func (b *beName) addNameScore(nameScore map[string]string) {
	b.scoreChan <- nameScore
}

func (b *beName) loop() {
	if bn.names == nil {
		bn.names = make(map[string]string)
	}

	if bn.nameChan == nil {
		bn.nameChan = make(chan string, 1)
	}

	if bn.scoreChan == nil {
		bn.scoreChan = make(chan map[string]string, 1)
	}

	for {
		select {
		case name := <- b.nameChan:
			go b.getScore(name)
		case nameScore := <- b.scoreChan:
			b.lock.Lock()
			for k, v := range nameScore {
				b.names[k] = v
				fmt.Println(k, ":", v)
				LOG(k + ":" + v)
			}
			b.lock.Unlock()
		}
	}
}

func (b *beName) getScore(name string) {
	nameScore := make(map[string]string)
	nameScore[name] = GetNameScore(name)
	if nameScore[name] != "" {
		b.addNameScore(nameScore)
	}

	wgTwo.Done()
}

type day struct {
	year	int
	month	int
	day		int
	hour	int
}

func (d *day) toString() string {
	return fmt.Sprintf("%d-%d-%d %d", d.year, d.month, d.day, d.hour)
}

//////////////////////////////////////////////////////

var bn = new(beName)

func init() {

	for {
		fmt.Println("请输入姓氏(李|张|王...)：")
		fmt.Scanln(&bn.surname)
		if bn.surname == "" {
			continue
		}

		break
	}

	for {
		fmt.Println("请输入性别(男|女)： ")
		fmt.Scanln(&bn.sex)

		if bn.sex == "男" || bn.sex == "女" {
			break
		}
	}

	d := &day{}

	for {
		var day string
		fmt.Println("请输入出生日期时间(2018-01-01 00:00:00)： ")
		fmt.Scanln(&day)

		if day == "" {
			continue
		}

		t, err := toDateTime(day)
		if err == nil {
			d.year 	= t.Year()
			d.month = int(t.Month())
			d.day 	= t.Day()
			d.hour 	= t.Hour()

			bn.birthday = *d

			break
		}
	}

	fmt.Println("姓氏:", bn.surname, "，性别:", bn.sex, "，出生日期:", bn.birthday.toString())
}

func url(page int) string {
	return fmt.Sprintf(
		"http://www.hzxljx.com/qiuming-detail.php?xing=%s&gender=%d&year=%d&month=%d&day=%d&hour=%d",
		bn.surname, bn.gender(), bn.birthday.year, bn.birthday.month, bn.birthday.day, bn.birthday.hour, page,
	)
}

func filterContext(context string) string  {
	//将HTML标签全转换成小写
	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
	context = re.ReplaceAllStringFunc(context, strings.ToLower)

	//去除STYLE
	re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
	context = re.ReplaceAllString(context, "")

	//去除SCRIPT
	re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
	context = re.ReplaceAllString(context, "")

	//去除换行符
	re, _ = regexp.Compile("\\s{2,}")
	context = re.ReplaceAllString(context, "")

	return context
}

func matchNames(context string) {
	var reg = regexp.MustCompile(`<li class='flo-left'>(.*?)</li>`)
	pages := reg.FindAllStringSubmatch(context, -1)

	for _, p:= range pages {
		wgTwo.Add(1)
		bn.addName(p[1])
	}

	wgOne.Done()
}

func matchMaxPage(context string) int {
	var reg = regexp.MustCompile(`<li class="page pageunm"><a.*?>(\d+)</a></li>`)
	pages := reg.FindAllStringSubmatch(context, -1)

	maxPage := 1
	for _, v:= range pages {
		if page, err := strconv.Atoi(v[1]); err == nil {
			if maxPage < page {
				maxPage = page
			}
		}
	}

	return maxPage
}

func GetNameScore(name string) string {
	names := strings.Split(name, "")

	params := make(map[string]string)

	for k, v := range names {
		if k == 0 {
			params["x"] = v
		} else {
			params["m"] = params["m"] + v
		}
	}

	context := httpPost("https://www.ceshidafen.com/xingmingzi/", params)

	var reg = regexp.MustCompile(`<strong>(\d+)</strong>`)
	score := reg.FindAllStringSubmatch(context, -1)

	if len(score) > 0 {
		return score[0][1]
	}

	return ""
}

var wgOne sync.WaitGroup
var wgTwo sync.WaitGroup

func main()  {
	runtime.GOMAXPROCS(runtime.NumCPU())

	go bn.loop()

	pc := httpGet(url(1))
	pc = filterContext(pc)
	maxPage := matchMaxPage(pc)

	for i :=1; i<= maxPage; i++ {
		wgOne.Add(1)
		go func() {
			nc := httpGet(url(i))
			nc = filterContext(nc)
			matchNames(nc)
		}()
	}

	wgOne.Wait()
	wgTwo.Wait()
}
